{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "090bed4c-5f16-4a97-9b63-8ee8b81fa2f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "df = pd.read_csv('df.csv')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4adbf696-8044-49ee-81ef-8426ee6ee458",
   "metadata": {},
   "source": [
    "### 数据清洗\n",
    "去重, 缺失值处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fac926d2-d35e-4b49-aa36-a4353eb91344",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.duplicated()  # 标记是否为重复行"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9dbb5ed9-ba00-43c9-8fcf-d6bdc170f3c0",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.drop_duplicates()  # 去重"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6392b189-ab1b-429b-a18c-addf48ff8600",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.drop_duplicates('换手率')  # 按指定字段去重, 重复行保留第一次出现的行"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "903aed98-5236-4217-ad94-6d151ffababe",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.isnull()  # 标记cell是否为nan, 相反notnull()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cddea4e2-c6c1-45be-9fd5-4eeb99f4268d",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.dropna()  # nan值行删除"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "29234703-34a1-4ac8-b612-c82352dc5889",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.dropna(how='all')  # 行数据都是nan才丢弃"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "271ecf7e-eef6-4cd6-ac4f-a42641175225",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.dropna(axis=1)  # 丢弃含nan的列"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ba8422a-5a3e-42c4-b674-98be4fc5ddee",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.fillna(0.1)  # 指定值填充"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4363eff1-e793-40cf-9994-533a91590f0b",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.ffill()  # 前值填充"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d7da0398-7798-47a0-8d4e-7b127e218142",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.bfill()  # 后值填充"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "061c39bb-b394-4984-8604-34e952351465",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.fillna(df['换手率'].mean())  # 列均值填充, 其他列统计值同理, 实际使用的value填充"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cddafa40-83da-4aa3-ba09-74c84738e204",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.fillna({'换手率': 0.01})  # dict填充"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "970b367e-9b4c-4263-8014-304bbe6e11b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "df['股票名称'].str.strip()\n",
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53e4a890-7cac-4af3-bb83-4428edda786e",
   "metadata": {},
   "source": [
    "### 数据抽取"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "08ff8077-d59e-4737-ae88-0a7bd664fb11",
   "metadata": {},
   "outputs": [],
   "source": [
    "df['成交额'] = df['成交额'].astype(str)  # 类型转换\n",
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5377baff-f560-4a96-88db-f3836e73ac2e",
   "metadata": {},
   "outputs": [],
   "source": [
    "df['成交额'] = df['成交额'].str.slice(0, -2)  # 字符串截取\n",
    "df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e58cc8fd-3a70-4ae5-b2df-10446ac8164b",
   "metadata": {},
   "outputs": [],
   "source": [
    "ndf = df['日期'].str.split('-', expand=True)  # 拆分新的df\n",
    "ndf.columns = ['year', 'month', 'day']\n",
    "ndf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aa7922d5-8da2-4694-a5d4-73e0b16deb0a",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.set_index('日期')   # 重置索引"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ccac3e4e-b9dc-48ad-a918-470dee38dcdf",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[df.日期 >= '2014-02-11']  # 比较条件 > < = !"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b8046d4-909d-4e5b-a770-841e1b28dcfb",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[df.日期.between('2014-02-08', '2014-02-11')]  # between 范围"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36fe4ccf-dada-4ed1-87f4-1d751c86ef7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[df.换手率.isnull()]  # 列含为值的行"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d1376661-715d-47e2-b5ae-7703df095907",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[df.日期.str.contains('-01-')]  # str判断"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bcd5d1db-99bd-4457-a8ca-f20f7d900ff7",
   "metadata": {},
   "outputs": [],
   "source": [
    "df[(df.振幅 > 5) | (df.涨跌额 > 0.1)]  # 位运算 & | ~"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0548c913-6b98-4597-b40d-3911b282d449",
   "metadata": {},
   "outputs": [],
   "source": [
    "d1 = {'a': '[1,2,3]', 'b': '[4,5,6]'}\n",
    "df1 = pd.DataFrame.from_dict(d1, orient='index')  # dict key作为df的行index, 指定为'columns' or 'tight'时要求dict符合要求, 以及必须报含的字段\n",
    "df1.index.name = 'key'  # 指定索引名\n",
    "df1 = df1.reset_index()  # 索引列作为数据列, 新增从0开始的行索引\n",
    "df1.columns = ['key', 'value']  # 重置列名\n",
    "df1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bfc5e650-a74f-4833-b257-d7e55d6de3dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "d1 = {'a': [1,2,3], 'b': [4,5,6]}\n",
    "df1 = pd.DataFrame.from_dict(d1,  orient='columns')  # 字典值数组长度要求一致\n",
    "df1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3a8d6613-e062-4b5d-9a98-3f8a3872e1d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "d1 = {'a': pd.Series([4,5,6]), 'b': pd.Series([4,6,9,8])}\n",
    "df2 = pd.DataFrame.from_dict(d1)  # Series时不要求长度相同, 缺失为nan\n",
    "df2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2b2ef1ac-593e-42d9-9186-814704cf8891",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.concat([df2.loc[:-1], df1, df2.loc[0:]])  # 行拼接, df1拼接到df2前面"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5f84eb6f-ebd2-4f16-bcb0-3e422a30ba57",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3 = pd.concat([df2.loc[0:], df1, df2.loc[:-1]])  # 行拼接, df1拼接到df2后面\n",
    "df3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "695ca880-e6da-45b2-8070-bc8fe959c7f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.reset_index().drop('index', axis=1) # 重置索引后drop index列"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f0e1ef14-cf15-456d-9aca-597fa628fb82",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.reset_index(drop=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f38d144-6afc-46d1-9239-eaf9f135cfa5",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.replace(9, 8) # 单值替换"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3633fed5-68ab-4143-b6e9-4b4bdd9d73d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.replace({'a': 2.0}, 2.5)  # 列值替换"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "89aea681-29fe-40dc-a56d-68c53ccb3e9f",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.replace({2.0: 2.5, 9: 8})  # 多值替换"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99e1cd37-ad33-4ed8-bda7-8da4c91f4fe6",
   "metadata": {},
   "source": [
    "### 索引排序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c8da8d22-3dd9-4a15-ae6e-5a1c18d2a2b0",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.Series([2,8,4,1], index=['a','g','d','b']).sort_index()  # 默认索引升序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1ad8348e-3149-42c1-95b3-b9004da30028",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.Series([2,8,4,1], index=['a','g','d','b']).sort_index(ascending=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9d115dab-d5c0-4e5f-b463-4f70664318f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.sort_index(ascending=False)  # 行索引倒序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "766cd461-c89f-4e5c-83b5-4aec4e952c82",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.columns = ['g', 'f']\n",
    "df3.sort_index(axis=1)  # 列索引升序"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2fceb100-c545-479f-9745-81c6992a42b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.Series([2,8,4,1], index=['a','g','d','b']).rank()  # 默认值升序排名, max"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "08e11452-9039-4c28-b8e7-380fc342fb24",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.Series([2,8,4,1], index=['a','g','d','b']).rank(method='first')  # 默认值升序排名"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7c56db44-a259-4755-96ce-1a4293efb680",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3['c'] = pd.Series([55,55,44,55,11,55,77], index=[0,1,2,3,0,1,2])\n",
    "df3, df3.rank()  # 每一列按照值升序排名, 相同排名默认取排名均值, 即在排在第5开始连续4个同值, 分数为 (5 + 6 + 7 + 8)  / 4 = 6.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b0818bfe-b99d-4005-a341-2753431c1f08",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.rank(method='max')  # 同排名取排名最大值, 同理还有min"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b2ea5c77-770d-4b43-bd3b-9cd3115254e0",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.rank(method='first')  # 同值按出现先后排名"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c535dd5-004c-4c22-9ca6-df32b863f75f",
   "metadata": {},
   "outputs": [],
   "source": [
    "df3.rank(method='first', axis=1, ascending=False)   # 此外可倒序, 列排"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3040214d-06c8-4e52-b5c0-75aa0f24bb23",
   "metadata": {},
   "outputs": [],
   "source": [
    "df4 = df3.reset_index(drop=True)  # 重设索引并丢弃原索引\n",
    "df4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b5d80b26-ccb8-4923-b527-03c2f5431830",
   "metadata": {},
   "outputs": [],
   "source": [
    "df4.reindex()  # 重新索引, 一般不用, 主要用于带参方法来筛选或扩展数据, 或重新排序; 不允许索引重复"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b9a9e242-e694-4593-9807-08e845f574b1",
   "metadata": {},
   "outputs": [],
   "source": [
    "df4.reindex(index=list(range(3, 8)))  # 重新索引, 默认空行为nan"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79d3eaea-3e45-4acd-934b-24d3b7776521",
   "metadata": {},
   "outputs": [],
   "source": [
    "df4.reindex(index=list(range(3, 8)), fill_value=0)  # 重新索引, 空行填充固定值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f68f34a-99d5-43ad-93a2-a94593ed2e6b",
   "metadata": {},
   "outputs": [],
   "source": [
    "df4.reindex(index=list(range(3, 8)), method='ffill')  # 重新索引, 空行填用前一行非空填充"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fdb650ca-bec6-427b-8be1-771cf5cffc6f",
   "metadata": {},
   "outputs": [],
   "source": [
    "df4.reindex(index=list(range(3, 9)), method='bfill')  # 重新索引, 空行用后一行非空填充"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "907dfe26-240b-4fd9-8b75-e89d8a288e32",
   "metadata": {},
   "outputs": [],
   "source": [
    "df4.reindex(columns=['c','f','d'], fill_value=5)  # 指定列重新索引, 不存在列填充固定值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7fa581ed-dd0c-454f-9f9a-edf986bd9c3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "df5 = df3.set_index('g', append=True)  # 将一组列取为索引, append到原索引上\n",
    "df5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3551d2a0-b223-4441-893b-88395345e588",
   "metadata": {},
   "outputs": [],
   "source": [
    "df5.reset_index(level='g')   # 重置索引, 将一组索引列还原为数据列"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e5096e4-cd08-413d-9e55-195d903a4d44",
   "metadata": {},
   "outputs": [],
   "source": [
    "df5.reset_index()  # 重置索引, 默认将所有索引列还原为数据列, 并新增一行索引"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "37bcabde-b94c-485e-9a1b-700bb9717b07",
   "metadata": {},
   "outputs": [],
   "source": [
    "df5.reset_index(drop=True)  # 重置索引, drop 控制原索引列是否丢弃"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2611bbf-2d96-48e7-9ab9-882cc0c15e93",
   "metadata": {},
   "source": [
    "### 数据合并"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "15d26cd8-bd67-4854-aa86-31dfd2b68a53",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.concat([df2, df1], ignore_index=True)   # 数据合并忽略索引, 并在第一个df索引上顺延"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f848d13-925f-4c34-b039-ce195603bcf4",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.merge(df1, df2, left_on='b', right_on='b')  \n",
    "# 数据列链接, 即sql join, 可用how = inner(默认), outer, left, right指定连接方式\n",
    "# on 指定两边同时存在的列, 列名不同可用left_on, right_on分别指定"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "30cf5a41-8c57-4847-bb30-3215a10a527c",
   "metadata": {},
   "source": [
    "### 数据计算"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6a444a95-dca1-4797-8753-484f85e03a8f",
   "metadata": {},
   "outputs": [],
   "source": [
    "df['成交额'] = df['成交额'].astype(int)\n",
    "df['均价'] = df['成交额'] / df['成交量']  # 基本的多列计算后生成结果列\n",
    "df"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "777240f0-7362-4ba1-9c49-dd01fb16100c",
   "metadata": {},
   "source": [
    "### 数据标准化(归一化)\n",
    "1. 处理量纲不同对数据分析的影响\n",
    "2. 两种归一化方法:\n",
    "   ```\n",
    "    离差标准化: X* = (x-min)/(max-min), x样本数据, max, min 样本最大最小值, 新加入数据需要重新计算\n",
    "    Z-score标准化: X* = (x-μ)/σ,  μ样本均值, σ样本标准差 \n",
    "   ```\n",
    "   [标准差公式](https://www.shuxuele.com/data/standard-deviation-formulas.html)  \n",
    "   [标准差或方差计算时, 被除样本数](https://blog.csdn.net/qq_34184505/article/details/127652164)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8b1eb040-2bef-421b-b56a-c35ff14248cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "jj = df['均价']\n",
    "(jj - jj.min()) / (jj.max() - jj.min())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d0b9ce8-4bb8-4e9a-a202-6ea7989d97be",
   "metadata": {},
   "outputs": [],
   "source": [
    "(jj - jj.mean()) / jj.std()  # 均值为0, 标准差为1的正态分布"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bc4d060c-5a16-4427-829e-e2178f34fbce",
   "metadata": {},
   "outputs": [],
   "source": [
    "jj.std(ddof=0), jj.var(), jj.std() ** 2, jj.std()  # pandas std 默认用 n-1 个样本(标准差偏大), 即ddof=1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5bd204a2-6bda-4275-8d26-11ca95150061",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "arr = np.array(jj)\n",
    "arr.std(), arr.std(ddof=1)  # numpy 默认用n个样本算标准差"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b471c7e8-e3ec-4744-a9fc-f2e5f52104ff",
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn import preprocessing as pp\n",
    "\n",
    "pp.scale(jj)  # 标准化"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "548fcdc1-7f45-493b-b1dc-5f1bad186c52",
   "metadata": {},
   "outputs": [],
   "source": [
    "cf = df[['涨跌额', '均价']]\n",
    "sc = pp.StandardScaler().fit(cf)\n",
    "sc.mean_, sc.scale_,sc.var_,sc.transform(cf)  # 均值, 标准差, 方差, 归一化结果(直接使用均值方差对测试数据转换)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ca9dea7-73ca-4234-ae8d-d71615f7eed1",
   "metadata": {},
   "source": [
    "### 数据分组"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f7ab2590-cdd1-4155-b14e-9529cd74a81c",
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.cut(jj, bins=[jj.min() - 1, 410, 420, 430, jj.max() + 1], labels=['底价', '低价', '震荡', '溢价'])  # 分组打标签"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40a55a64-effe-451a-af76-fd78e8446fae",
   "metadata": {},
   "source": [
    "### 日期处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d979860-24be-4137-af1b-6702f667915f",
   "metadata": {},
   "outputs": [],
   "source": [
    "dt = pd.to_datetime(df['日期'], format='%Y-%m-%d')  # 从字符串解析到datetime\n",
    "dt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "170e44bb-9f2f-4040-ae25-b70c8b85fe92",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "\n",
    "dt.apply(lambda x: datetime.strftime(x, '%Y.%m.%d'))  # datetime 解析成字符串"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "807abc3b-3ecd-447d-9a44-82a364daa6a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "dt.dt.weekday  # 日期抽取可得到second, minute, hour, day, month, year, weekday"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6503ea2b-fd8a-4a64-a423-2cdc66ffe672",
   "metadata": {},
   "outputs": [],
   "source": [
    "df.isnull().any()  # 列是否存在空值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a9c2cc25-db26-4c9e-be56-5f5a6c1dcdb9",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
